Skip to content

fix(devframe): reject non-loopback DNS origins beginning with 127. - #319

Open
antfubot wants to merge 1 commit into
mainfrom
fix/loopback-origin-dns-rebinding
Open

fix(devframe): reject non-loopback DNS origins beginning with 127.#319
antfubot wants to merge 1 commit into
mainfrom
fix/loopback-origin-dns-rebinding

Conversation

@antfubot

Copy link
Copy Markdown
Collaborator

Summary

Devframe's WebSocket/SSE/MCP origin gate treated any hostname beginning with 127. as loopback, so an attacker-controlled DNS name such as 127.attacker.example or 127.0.0.1.attacker.example slipped past the loopback trust boundary. Because every transport (ws-server, ws-bun, ws-deno, sse-server, the MCP fetch handler) and the external-viewer origin registry all funnel their origin check through isLoopbackHostname, a cross-origin browser page could defeat the DNS-rebinding / cross-site WebSocket-hijacking mitigation and reach the privileged RPC surface (e.g. build-mode DevTools where accepted clients are auto-trusted, then the terminals RPC → shell command execution).

The root cause was a prefix test:

h.startsWith('127.')

which never verified the hostname was an IPv4 literal.

Fix

Classify the IPv4 loopback case structurally: the whole hostname must be a canonical dotted-decimal literal in 127.0.0.0/8. Genuine loopback addresses (127.0.0.1, 127.5.5.5) stay allowed while 127.* DNS names are rejected. The localhost / *.localhost / ::1 branches are unchanged.

The fix lives in the single shared classifier, so it closes the bypass for the WS Node/Bun/Deno transports, the SSE transport, the route-based MCP endpoint, and the origin registry at once.

Tests

Extended the origin-check unit test with regression cases:

Origin hostname Result
localhost, foo.localhost, 127.0.0.1, 127.5.5.5, ::1 allow
127.attacker.example, 127.0.0.1.attacker.example, 127.0.0, 127.0.0.256, 1270.0.0.1, evil.example reject

isAllowedOrigin is likewise checked to accept http://127.0.0.1:5173 and reject http://127.attacker.example.

Audit

Confirmed isLoopbackHostname is the only place this classification is implemented; all other origin gates delegate to it. The public API surface is unchanged (only private helpers were added), so the tsnapi snapshot is unaffected.

  • CWE-346: Origin Validation Error
  • CWE-1385: Missing Origin Validation in WebSockets

This PR was created with the help of an agent.

isLoopbackHostname classified any hostname starting with '127.' as
loopback, so an attacker-controlled DNS name like 127.attacker.example
passed the loopback origin gate that guards the RPC/MCP surface. Because
every WS/SSE/MCP transport and the origin registry funnel through this
check, a cross-origin browser page could defeat the DNS-rebinding /
cross-site WebSocket-hijacking mitigation and reach privileged RPC.

Match the IPv4 loopback case structurally instead: the whole hostname
must be a canonical dotted-decimal literal in 127.0.0.0/8. Genuine
loopback addresses (127.0.0.1, 127.5.5.5) stay allowed; 127.* DNS names
are rejected.

CWE-346, CWE-1385
@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
devframe Skipped Skipped Aug 31, 2026 5:25am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant